
#include "DeleDAPI.h"

#define PR_GETINSTALLDIR        0   //  used to get DeleD installation directory
#define PR_GETMEM               1   //  used to know how long is the string I want to obtain from DeleD,
                                    //  or better how much memory I need to allocate for getting data
#define PR_GETDATA              2   //  used to retrieve data from DeleD
#define PR_SETDATA              3   //  used to pass data to DeleD

#define HI_SCENEPROPERTIES      0   //  used for setting or getting scene properties
#define HI_OBJECTS              1   //  used for setting or getting all objects
#define HI_LIGHTS               2   //  used for setting or getting all lights
#define HI_MATERIALS            3   //  used for setting or getting materials

#define HI_SELECTEDOBJECTS      10  //  used for setting or getting only selected objects
#define HI_SELECTEDLIGHTS       11  //  used for setting or getting only selected lights
#define HI_SELECTEDMATERIALS    12  //  used for setting or getting only selected materials

#define PE_NOERROR              0   //  no errors in callback
#define PE_NODATA               1   //  no data to set or get
#define PE_INVALIDVERSION       2   //  invalid version
#define PE_INVALIDMEM           3   //  invalid memory size
#define PE_INVALIDGET           4   //  invalid get operation
#define PE_INVALIDSET           5   //  invalid set operation
#define PE_INVALIDHEADER        6   //  invalid header
#define PE_INVALIDDATASTRING    7   //  invalid data string
#define PE_INVALIDDATAPROPERTY  8   //  invalid data property
#define PE_OBJECTDATA           11  //  invalid object data
#define PE_LIGHTDATA            12  //  invalid light data
#define PE_MATERIALDATA         13  //  invalid material data
#define PE_UNKNOWNREASON        98  //  callback reason unknown
#define PE_UNKNOWNERROR         99  //  unknown error

#include <vector>

static struct {
  uint error;
  char const * str;
}
gErrors[] = {
  { PE_NOERROR, 0 },
  { PE_NODATA, "No data available." },
  { PE_INVALIDVERSION, "The plug-in does not support this version of DeleD." },
  { PE_INVALIDMEM, "The amount of memory supplied was not sufficient." },
  { PE_INVALIDGET, "The get operation is not supported." },
  { PE_INVALIDSET, "The set operation is not supported." },
  { PE_INVALIDHEADER, "The header is mal-formed." },
  { PE_INVALIDDATASTRING, "The data string is mal-formed." },
  { PE_INVALIDDATAPROPERTY, "The data property is mal-formed." },
  { PE_OBJECTDATA, "The data supplied for an object is mal-formed." },
  { PE_LIGHTDATA, "The data supplied for a light is mal-formed." },
  { PE_MATERIALDATA, "The data supplied for a material is mal-formed." },
  { PE_UNKNOWNREASON, "The reason for the callback is not supported." },
  { PE_UNKNOWNERROR, "The cause of the error is shrouded in mystery." },
};

char const * DeleDError(uint error)
{
  for (size_t i = 0; i < sizeof(gErrors)/sizeof(gErrors[0]); ++i) {
    if (error == gErrors[i].error) {
      return gErrors[i].str;
    }
  }
  return "The Gods are against us. We cannot prevail.";
}

#if 0

#include <windows.h>

#include <stdarg.h>

class Timer {
  public: 
    Timer() {
      long long pf;
      ::QueryPerformanceFrequency((LARGE_INTEGER*)&pf);
      mult_ = 1.0/(double)pf;
      ::QueryPerformanceCounter((LARGE_INTEGER*)&base_);
    }
    double Now() {
      long long pc;
      ::QueryPerformanceCounter((LARGE_INTEGER*)&pc);
      return double(pc-base_) * mult_;
    }
    long long base_;
    double mult_;
};
Timer timer;

static double Now()
{
  return timer.Now();
}

class LogFileHandle {
  public:
    LogFileHandle() {
      logFile_ = fopen("c:\\logfile.txt", "wb");
    }
    ~LogFileHandle() {
      fclose(logFile_);
    }
    FILE * logFile_;
};
LogFileHandle lfh;

static void Log(char const * fmt, ...)
{
  va_list vl;
  va_start(vl, fmt);
  vfprintf(lfh.logFile_, fmt, vl);
  va_end(vl);
}
#else

#define Log (void)
#define Now() 0

#endif

namespace DeleD {

  template<typename T>
  class ItfImpl : public IInterface<T>
  {
    public:
      ~ItfImpl()
      {
        if (vec_.size()) {
          destroy(&vec_[0]);
        }
      }
      virtual size_t count()
      {
        return vec_.size();
      }
      virtual T const * begin()
      {
        return &(*vec_.begin());
      }
      virtual T const * end()
      {
        return &(*vec_.end());
      }
      virtual T const & item(size_t ix)
      {
        assert(ix < vec_.size());
        return vec_[ix];
      }

      template<typename Q>
      void destroy(Q ** pptr)
      {
        //  delete the pointers
        for (std::vector<T>::iterator ptr = vec_.begin(), end = vec_.end();
            ptr != end; ++ptr) {
          delete *ptr;
        }
      }
      template<typename P>
      void destroy(P * pptr)
      {
        pptr = 0;
        //  do nothing
      }
      std::vector<T> vec_;
  };
  
  class Scene : public IScene
  {
    public:
      Scene(TCallback cb) : cb_(cb)
      {
        buffer_ = tempBuffer_;
        bufferSize_ = sizeof(tempBuffer_);
        end_ = 0;
      }
      ~Scene()
      {
        if (buffer_ != tempBuffer_) {
          delete[] buffer_;
        }
      }
      virtual void dispose()
      {
        delete this;
      }
      virtual char const * name()
      {
        return name_.c_str();
      }
      virtual IInterface<IObject *> * objects()
      {
        return &objects_;
      }
      virtual IInterface<IMaterial *> * materials()
      {
        return &materials_;
      }
      virtual IInterface<ILight *> * lights()
      {
        return &lights_;
      }

      char * buffer(size_t size = 0)
      {
        if (size > bufferSize_) {
          if (buffer_ != tempBuffer_) {
            delete[] buffer_;
          }
          buffer_ = new char[size];
          end_ = 0;
        }
        return buffer_;
      }
      char * end()
      {
        assert(end_ != 0);
        return end_;
      }
      uint bufferSize()
      {
        return (uint)bufferSize_;
      }
      void op(char const * str)
      {
        Log("Beginning op(%s): %g\n", str, Now());
        end_ = 0;
        size_t l = strlen(str);
        for (size_t i = 10, j = 2; i < bufferSize(); i *= 10, ++j) {
          if (l < i-j) {
            l += j;
            goto done;
          }
        }
        throw "Operations bigger than what can fit in Scene::op()";
      done:
        sprintf(buffer(), "%ld;%s", l, str);
        TCbData cbdata(PR_GETMEM);
        cbdata.data_ = buffer();
        cbdata.size_ = 0;
        uint error = (*cb_)(&cbdata);
        if (error != 0) {
          throw DeleDError(error);
        }
        if (cbdata.size_ == 0) {
          end_ = buffer_;
          Log("No data in op(%s): %g\n", str, Now());
          return;
        }
        cbdata.data_ = buffer(cbdata.size_+1);
        sprintf(buffer(), "%ld;%s", l, str);
        cbdata.data_[cbdata.size_] = 0;
        cbdata.reason_ = PR_GETDATA;
        error = (*cb_)(&cbdata);
        if (error) {
          throw DeleDError(error);
        }
        end_ = cbdata.size_ + buffer_;
        Log("Ending op(%s): %g\n", str, Now());
      }

      void readLights();
      void readMaterials();
      void readObjects();

      std::string           name_;
      ItfImpl<IObject *>    objects_;
      ItfImpl<IMaterial *>  materials_;
      ItfImpl<ILight *>     lights_;

      TCallback             cb_;

      char                  tempBuffer_[128];
      char *                buffer_;
      char *                end_;
      size_t                bufferSize_;
  };

  class Light : public ILight {
    public:
      Light()
      {
        //  I do some skanky casts in the storage below, so 
        //  make sure that my assumptions about memory layout 
        //  are correct.
        assert(sizeof(PointLight) == sizeof(DirectionalLight));
        assert(sizeof(DirectionalLight) == sizeof(SpotLight));
        assert(&((PointLight*)0)->center == &((DirectionalLight*)0)->center);
        assert(&((PointLight*)0)->center == &((SpotLight*)0)->center);
        assert(&((PointLight*)0)->id == &((DirectionalLight*)0)->id);
        assert(&((PointLight*)0)->id == &((SpotLight*)0)->id);
        memset(&light_, 0, sizeof(light_));
      }
      virtual char const * name()
      {
        return name_.c_str();
      }
      virtual LightType type()
      {
        return type_;
      }
      virtual PointLight const & point()
      {
        return light_;
      }
      virtual DirectionalLight const & directional()
      {
        return (DirectionalLight const &)light_;
      }
      virtual SpotLight const & spot()
      {
        return (SpotLight const &)light_;
      }

      std::string name_;
      LightType type_;
      PointLight light_;
  };

  class Material : public IMaterial
  {
    public:
      Material()
        : id_(0)
      {
      }
      ~Material()
      {
      }
      virtual IInterface<ILayer *> * layers()
      {
        return &layers_;
      }
      virtual int id()
      {
        return id_;
      }
      virtual char const * category()
      {
        return category_.c_str();
      }
      virtual char const * name()
      {
        return name_.c_str();
      }

      ItfImpl<ILayer *> layers_;
      int id_;
      std::string name_;
      std::string category_;
  };

  class Layer : public ILayer
  {
    public:
      Layer()
        : type_(0)
        , blend_(0)
      {
      }
      virtual int type()
      {
        return type_;
      }
      virtual char const * texture()
      {
        return texture_.c_str();
      }
      virtual int blend()
      {
        return blend_;
      }
      int type_;
      int blend_;
      std::string texture_;
  };

  class Object : public IObject
  {
    public:
      Object()
      {
      }
      ~Object()
      {
      }
      virtual char const * name()
      {
        return name_.c_str();
      }
      virtual char const * tag()
      {
        return tag_.c_str();
      }
      virtual IInterface<Float3> * vertices()
      {
        return &vertices_;
      }
      virtual IInterface<Poly> * polygons()
      {
        return &polygons_;
      }

      std::string name_;
      std::string tag_;
      int id_;
      int type_;
      int visible_;
      int snapmethod_;
      int groupid_;
      int auto_uv_;

      ItfImpl<Float3> vertices_;
      ItfImpl<Poly> polygons_;
  };
}

using namespace DeleD;


AutoDispose<IScene> NewScene(TCallback cb)
{
  Scene * s = new Scene(cb);
  s->readLights();
  s->readMaterials();
  s->readObjects();
  return AutoDispose<IScene>(s);
}

class ParseState {
  public:
    ParseState(char const * buf, char const * end)
      : buf_(buf)
      , end_(end)
      , orig_(buf)
    {
      assert(buf_ <= end_);
      initArray(isFloatChar_, "0123456789.Ee-+ ");
      initArray(isLongChar_, "0123456789-+ ");
    }
    void initArray(bool * a, char const * c)
    {
      memset(a, 0, sizeof(bool)*256);
      while (*c) {
        a[*c] = true;
        ++c;
      }
    }
    bool isFloatChar_[256];
    bool isLongChar_[256];
    bool atEnd()
    {
      assert(buf_ <= end_);
      return buf_ == end_;
    }
    long aLong(char delim = ';')
    {
      if (atEnd()) {
        throw "Asking for long at end of data in ParseState";
      }
      char buf[41];
      char * oBuf = buf;
      char * oEnd = &buf[40];
      while (buf_ < end_ && isLongChar_[*(unsigned char *)buf_] && oBuf < oEnd) {
        *oBuf++ = *buf_++;
      }
      *oBuf = 0;
      char * o = 0;
      long r = strtol(buf, &o, 10);
      if (o == 0 || o == buf || *buf_ != delim) {
        throw "Unexpected data format getting long in ParseState";
      }
      ++buf_;
      assert(buf_ <= end_);
      return r;
    }
    float aFloat(char delim= ';')
    {
      if (atEnd()) {
        throw "Asking for float at end of data in ParseState";
      }
      char buf[41];
      char * oBuf = buf;
      char * oEnd = &buf[40];
      while (buf_ < end_ && isFloatChar_[*(unsigned char *)buf_] && oBuf < oEnd) {
        *oBuf++ = *buf_++;
      }
      *oBuf = 0;
      char * o = 0;
      float r = float(strtod(buf, &o));
      if (o == 0 || o == buf || *buf_ != delim) {
        throw "Unexpected data format getting float in ParseState";
      }
      ++buf_;
      assert(buf_ <= end_);
      return r;
    }
    std::string aString(char delim = ';')
    {
      if (atEnd()) {
        throw "Asking for string at end of data in ParseState";
      }
      char const * o = strchr(buf_, delim);
      if (o == 0 || *o != delim) {
        throw "Unexpected data format getting string in ParseState";
      }
      std::string ret(buf_, o-buf_);
      buf_ = const_cast<char *>(o)+1;
      assert(buf_ <= end_);
      return ret;
    }
    char peek()
    {
      if (atEnd()) {
        throw "Asking for peek at end of data in ParseState";
      }
      return *buf_;
    }
    void skip(size_t n)
    {
      buf_ += n;
      assert(buf_ <= end_);
    }
    void seek(size_t pos)
    {
      buf_ = orig_ + pos;
      assert(buf_ <= end_);
    }
    size_t pos() const
    {
      return buf_ - orig_;
    }
    char const * orig_;
    char const * buf_;
    char const * end_;
};

void Scene::readLights()
{
  Log("Beginning readLights(): %g\n", Now());
  op("1;091;2,0,0,0;");
  ParseState ps1(buffer(), end());
  long hlen = ps1.aLong();
  (void)ps1.aLong();
  (void)ps1.aLong();
  (void)ps1.aLong(',');
  long nLights = ps1.aLong(',');
  long offset = ps1.aLong(',') + hlen - 1;
  long nBytes = ps1.aLong();
  ParseState ps(buffer()+offset, buffer()+offset+nBytes);
  lights_.vec_.resize(nLights);
  for (int n = 0; n < nLights; ++n) {
    Light * l = new Light;
    long type = ps.aLong();
    l->type_ = (LightType)type;
    switch (type) {
    case 1:   //  point light
      l->light_.constant = ps.aFloat();
      l->light_.linear = ps.aFloat();
      l->light_.quadratic = ps.aFloat();
      l->light_.range = ps.aLong();
      l->light_.center.x_ = ps.aFloat();
      l->light_.center.y_ = ps.aFloat();
      l->light_.center.z_ = ps.aFloat();
      l->light_.flash = ps.aLong();
      l->light_.active = ps.aLong();
      l->light_.diffuse.r_ = (uchar)ps.aLong();
      l->light_.diffuse.g_ = (uchar)ps.aLong();
      l->light_.diffuse.b_ = (uchar)ps.aLong();
      l->light_.diffuse.a_ = 255;
      l->light_.specular.r_ = (uchar)ps.aLong();
      l->light_.specular.g_ = (uchar)ps.aLong();
      l->light_.specular.b_ = (uchar)ps.aLong();
      l->light_.specular.a_ = 0;
      l->light_.intensity = ps.aLong();
      l->light_.id = ps.aLong();
      break;
    case 2:   //  directional light
      l->light_._reserved1.x_ = ps.aFloat();  //  direction.x
      l->light_._reserved1.y_ = ps.aFloat();  //  direction.y
      l->light_._reserved1.z_ = ps.aFloat();  //  direction.z
      l->light_.center.x_ = ps.aFloat();
      l->light_.center.y_ = ps.aFloat();
      l->light_.center.z_ = ps.aFloat();
      l->light_.flash = ps.aLong();
      l->light_.active = ps.aLong();
      l->light_.diffuse.r_ = (uchar)ps.aLong();
      l->light_.diffuse.g_ = (uchar)ps.aLong();
      l->light_.diffuse.b_ = (uchar)ps.aLong();
      l->light_.diffuse.a_ = 255;
      l->light_.specular.r_ = (uchar)ps.aLong();
      l->light_.specular.g_ = (uchar)ps.aLong();
      l->light_.specular.b_ = (uchar)ps.aLong();
      l->light_.specular.a_ = 0;
      l->light_.intensity = ps.aLong();
      l->light_.id = ps.aLong();
      break;
    case 3:   //  spot light
      l->light_.constant = ps.aFloat();
      l->light_.linear = ps.aFloat();
      l->light_.quadratic = ps.aFloat();
      l->light_.range = ps.aLong();
      l->light_._reserved1.x_ = ps.aFloat();    //  direction.x
      l->light_._reserved1.y_ = ps.aFloat();    //  direction.y
      l->light_._reserved1.z_ = ps.aFloat();    //  direction.z
      l->light_._reserved2 = ps.aFloat();       //  innerangle
      l->light_._reserved3 = ps.aFloat();       //  outerangle
      l->light_._reserved4 = ps.aFloat();       //  falloff
      l->light_.center.x_ = ps.aFloat();
      l->light_.center.y_ = ps.aFloat();
      l->light_.center.z_ = ps.aFloat();
      l->light_.flash = ps.aLong();
      l->light_.active = ps.aLong();
      l->light_.diffuse.r_ = (uchar)ps.aLong();
      l->light_.diffuse.g_ = (uchar)ps.aLong();
      l->light_.diffuse.b_ = (uchar)ps.aLong();
      l->light_.diffuse.a_ = 255;
      l->light_.specular.r_ = (uchar)ps.aLong();
      l->light_.specular.g_ = (uchar)ps.aLong();
      l->light_.specular.b_ = (uchar)ps.aLong();
      l->light_.specular.a_ = 0;
      l->light_.intensity = ps.aLong();
      l->light_.id = ps.aLong();
      break;
    default:
      throw "Unknown light type encountered.";
    }
    l->name_ = ps.aString();
    lights_.vec_[n] = l;
  }
  assert(ps.atEnd());
  Log("Ending readLights(): %g\n", Now());
}

void Scene::readMaterials()
{
  Log("Beginning readMaterials(): %g\n", Now());
  op("1;091;3,0,0,0;");
  ParseState ps1(buffer(), end());
  long hlen = ps1.aLong();
  (void)ps1.aLong();
  (void)ps1.aLong();
  (void)ps1.aLong(',');
  long nMaterials = ps1.aLong(',');
  long offset = ps1.aLong(',') + hlen - 1;
  long nBytes = ps1.aLong();
  ParseState ps(buffer()+offset, buffer()+offset+nBytes);
  materials_.vec_.resize(nMaterials);
  for (int n = 0; n < nMaterials; ++n) {
    Material * m = new Material();
    m->id_ = ps.aLong();
    m->name_ = ps.aString();
    m->category_ = ps.aString();
    (void)ps.aString();
    int nLayers = ps.aLong();
    for (int i = 0; i < nLayers; ++i) {
      if (ps.peek() == ';') {
        ps.skip(1);
        //  empty layer
      }
      else {
        Layer * l = new Layer();
        l->type_ = ps.aLong(',');
        l->texture_ = ps.aString(',');
        l->blend_ = ps.aLong();
        m->layers_.vec_.push_back(l);
      }
    }
    materials_.vec_[n] = m;
  }
  assert(ps.atEnd());
  Log("Ending readMaterials(): %g\n", Now());
}

void Scene::readObjects()
{
  Log("Beginning readObjects(): %g\n", Now());
  op("1;091;1,0,0,0;");
  ParseState ps1(buffer(), end());
  long hlen = ps1.aLong();
  (void)ps1.aLong();
  (void)ps1.aLong();
  (void)ps1.aLong(',');
  long nObjects = ps1.aLong(',');
  long offset = ps1.aLong(',') + hlen - 1;
  long nBytes = ps1.aLong();
  ParseState ps(buffer()+offset, buffer()+offset+nBytes);
  objects_.vec_.resize(nObjects);
  std::vector<uint> mLayerCount;
  uint nMat = (uint)materials_.count();
  mLayerCount.resize(nMat);
  for (uint j = 0; j < nMat; ++j) {
    mLayerCount[j] = (uint)materials_.item(j)->layers()->count();
  }
  for (int i = 0; i < nObjects; ++i) {
    Object * o = new Object();
    o->name_ = ps.aString();
    o->id_ = ps.aLong();
    o->type_ = ps.aLong();
    o->visible_ = ps.aLong();
    o->snapmethod_ = ps.aLong();
    o->groupid_ = ps.aLong();
    o->auto_uv_ = ps.aLong();
    o->tag_ = ps.aString();
    int nVertices = ps.aLong();
    o->vertices_.vec_.resize(nVertices);
    for (int v = 0; v < nVertices; ++v) {
      Float3 & f3 = o->vertices_.vec_[v];
      f3.x_ = ps.aFloat();
      f3.y_ = ps.aFloat();
      f3.z_ = ps.aFloat();
    }
    int nPolys = ps.aLong();
    o->polygons_.vec_.resize( nPolys );
    for (int p = 0; p < nPolys; ++p) {
      int nVerts = ps.aLong();
      uint mat = ps.aLong();
      uint mCount = mLayerCount[mat];
      Poly & poly = o->polygons_.vec_[p];
      poly.rewrite(mCount, nVerts);
      poly.material_ = mat;
      for (int q = 0; q < nVerts; ++q) {
        poly.indices()[q] = ps.aLong();
      }
      for (uint s = 0; s < mCount; ++s) {
        FixedArray<Float2> & uv = poly.coords(s);
        for (int q = 0; q < nVerts; ++q) {
          Float2 & r = uv[q];
          r.u_ = ps.aFloat();
          r.v_ = ps.aFloat();
        }
      }
      o->polygons_.vec_.push_back(poly);
    }
    objects_.vec_[i] = o;
  }
  assert(ps.atEnd());
  Log("Ending readObjects(): %g\n", Now());
}

